/*
  This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the
  MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

  Copyright Buildbot Team Members
*/

import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {BaseClass} from './classes/BaseClass';
import {IDataDescriptor} from './classes/DataDescriptor';
import {WebSocketClient} from './WebSocketClient';
import {MockWebSocket} from './MockWebSocket';
import {RequestQuery} from './DataQuery';
import {BaseDataAccessor, IDataAccessor} from './DataAccessor';
import {RestClient} from './RestClient';
import {DataClient} from './DataClient';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

class TestDataClass extends BaseClass {
  testdata: string = '';
  testid: number = 0;

  constructor(accessor: BaseDataAccessor, object: any) {
    super(accessor, 'tests', String(object.testid));
    this.update(object);
  }

  update(object: any) {
    this.testid = object.testid;
    this.testdata = object.testdata;
  }

  getProperties(query: RequestQuery = {}) {
    return this.getPropertiesImpl('properties', query);
  }

  toObject() {
    return {
      testid: this.testid,
      testdata: this.testdata,
    };
  }

  static getAll(accessor: IDataAccessor, query: RequestQuery = {}) {
    return accessor.get<TestDataClass>('tests', query, testDescriptor);
  }
}

class TestDescriptor implements IDataDescriptor<TestDataClass> {
  restArrayField = 'tests';
  fieldId: string = 'testid';

  parse(accessor: BaseDataAccessor, object: any) {
    return new TestDataClass(accessor, object);
  }
}

const testDescriptor = new TestDescriptor();

describe('DataPropertiesCollection', () => {
  const rootUrl = 'http://test.example.com/api/';
  let mock: MockAdapter;
  let restClient: RestClient;
  let webSocket: MockWebSocket;
  let webSocketClient: WebSocketClient;
  let client: DataClient;

  beforeEach(() => {
    vi.useFakeTimers({toFake: ['nextTick']});
    mock = new MockAdapter(axios);
    restClient = new RestClient(rootUrl);
    webSocket = new MockWebSocket();
    webSocket.readyState = webSocket.OPEN;
    webSocketClient = new WebSocketClient('url', (_) => webSocket);
    client = new DataClient(restClient, webSocketClient);
  });

  afterEach(() => {
    mock.reset();
  });

  const flushPromisesAndTimers = async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
    await vi.runAllTimersAsync();
  };

  it('new properties to empty properties', async () => {
    mock.onGet(rootUrl + 'tests').reply(200, {
      tests: [
        {
          testid: 12,
          testdata: 'c12',
        },
      ],
    });

    const tests = TestDataClass.getAll(client.open());
    await flushPromisesAndTimers();

    expect(webSocket.parsedSendQueue).toEqual([
      {
        _id: 1,
        cmd: 'startConsuming',
        path: 'tests/*/*',
      },
    ]);
    webSocket.clearSendQueue();
    webSocket.respond(JSON.stringify({_id: 1, msg: 'OK', code: 200}));
    await flushPromisesAndTimers();

    expect(tests.array.map((e) => e.toObject())).toEqual([{testid: 12, testdata: 'c12'}]);

    mock.onGet(rootUrl + 'tests/12/properties').reply(200, {properties: [{}]});
    const properties = tests.array[0].getProperties();

    await flushPromisesAndTimers();

    expect(Object.fromEntries(properties.properties)).toEqual({});

    expect(webSocket.parsedSendQueue).toEqual([
      {
        _id: 2,
        cmd: 'startConsuming',
        path: 'tests/12/properties/*',
      },
    ]);
    webSocket.clearSendQueue();

    webSocket.respond(JSON.stringify({_id: 2, msg: 'OK', code: 200}));
    await flushPromisesAndTimers();

    // respond with new properties
    webSocket.respond(
      JSON.stringify({
        k: 'tests/12/properties/update',
        m: {prop1: 51, prop2: 'p52'},
      }),
    );
    await flushPromisesAndTimers();

    expect(Object.fromEntries(properties.properties)).toEqual({prop1: 51, prop2: 'p52'});

    // respond with property updates
    webSocket.respond(
      JSON.stringify({
        k: 'tests/12/properties/update',
        m: {prop1: 52, prop2: 'p53', prop3: 3},
      }),
    );
    await flushPromisesAndTimers();

    expect(Object.fromEntries(properties.properties)).toEqual({prop1: 52, prop2: 'p53', prop3: 3});
  });

  it('new properties to non empty properties', async () => {
    mock.onGet(rootUrl + 'tests').reply(200, {
      tests: [
        {
          testid: 12,
          testdata: 'c12',
        },
      ],
    });

    const tests = TestDataClass.getAll(client.open());
    await flushPromisesAndTimers();

    expect(webSocket.parsedSendQueue).toEqual([
      {
        _id: 1,
        cmd: 'startConsuming',
        path: 'tests/*/*',
      },
    ]);
    webSocket.clearSendQueue();
    webSocket.respond(JSON.stringify({_id: 1, msg: 'OK', code: 200}));
    await flushPromisesAndTimers();

    expect(tests.array.map((e) => e.toObject())).toEqual([{testid: 12, testdata: 'c12'}]);

    mock.onGet(rootUrl + 'tests/12/properties').reply(200, {
      properties: [
        {
          prop1: 51,
          prop2: 'p52',
        },
      ],
    });

    const properties = tests.array[0].getProperties();

    await flushPromisesAndTimers();

    expect(webSocket.parsedSendQueue).toEqual([
      {
        _id: 2,
        cmd: 'startConsuming',
        path: 'tests/12/properties/*',
      },
    ]);
    webSocket.clearSendQueue();

    webSocket.respond(JSON.stringify({_id: 2, msg: 'OK', code: 200}));
    await flushPromisesAndTimers();

    expect(Object.fromEntries(properties.properties)).toEqual({prop1: 51, prop2: 'p52'});

    // respond with property updates
    webSocket.respond(
      JSON.stringify({
        k: 'tests/12/properties/update',
        m: {prop1: 52, prop2: 'p53', prop3: 3},
      }),
    );
    await flushPromisesAndTimers();

    expect(Object.fromEntries(properties.properties)).toEqual({prop1: 52, prop2: 'p53', prop3: 3});
  });
});
